/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * Read data from id5db compressed database file
 * Google snappy library needed
 * 
 * ADDITIONAL STANDALONE FILE of id5db
 * By Dr.NP <np@bsgroup.org>
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <err.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <snappy-c.h>

#ifndef _POSIX_PATH_MAX
    #define _POSIX_PATH_MAX 1024
#endif

#ifndef _ISOC99_SOURCE
    #define _ISOC99_SOURCE
#endif

#define DEFAULT_ITEM_SIZE 16384
#define DB_FILE_SIZE 1073741824
#define SHM_DIR "/dev/shm"
#define MMAP_FILE_COMPRESSED_SUFFIX ".tar.id5"
#define DB_TMP_SUFFIX ".id5"

static int tmp_scanner(const struct dirent *unused)
{
	return 1;
}

int main(int argc, char **argv)
{
	int c;
	char *db_folder = "./";
	unsigned long long id = 0;
	int item_size = DEFAULT_ITEM_SIZE;
	char db_file[_POSIX_PATH_MAX];
	char tmp_file[_POSIX_PATH_MAX];
	
	while (-1 != (c = getopt(argc, argv, 
		"f:"
		"i:"
		"s:"
		"h"
	)))
	{
		switch (c)
		{
			case 'f' : 
				db_folder = strdup(optarg);
				break;
			
			case 'i' : 
				id = strtoull(optarg, NULL, 10);
				break;
			
			case 's' : 
				item_size = atoi(optarg);
				break;
			
			case 'h' : 
				printf("Data exporter of id5db. Version 1.0 by Dr.NP <np@bsgroup.org>\n");
				printf("USAGE : dbreader -i #ITEM_ID# [-f #DATABASE_FOLDER#] [-s #IEM_SIZE#]\n");
				printf("\t-h to display this help topic.\n");
				printf("Return value :\n");
				printf("\t0 - Read successfully\n");
				printf("\t1001 - Operate on /dev/shm error\n");
				printf("\t1002 - Cannot open database file\n");
				printf("\t1003 - Memory alloc error\n");
				printf("\t1004 - Snappy decompress failed\n");
				printf("\t1005 - Create original data file error\n");
				printf("\t1006 - Create result error\n\n");
				return 0;
				break;
			
			default : 
				break;
		}
	}
	
	unsigned long int file_id = (id * item_size) / DB_FILE_SIZE;
	unsigned long int offset = (id * item_size) % DB_FILE_SIZE;
	snprintf(db_file, _POSIX_PATH_MAX - 1, "%s/%08X%s", db_folder, (unsigned int) file_id, MMAP_FILE_COMPRESSED_SUFFIX);
	snprintf(tmp_file, _POSIX_PATH_MAX - 1, "%s/%08X%s", SHM_DIR, (unsigned int) file_id, DB_TMP_SUFFIX);
	printf("Get id %llu from %s to %s with size %d at offset %ld\n", id, db_file, tmp_file, item_size, offset);
	
	struct stat st;
	int fd;
	fd = open(tmp_file, O_RDONLY, (mode_t) 0666);
	if (-1 == fd)
	{
		// Cannot open file, delete all tmp file fisrt
		struct dirent **eps;
		int n;
		char chk_tmp_file[_POSIX_PATH_MAX];
		n = scandir(SHM_DIR, &eps, tmp_scanner, alphasort);
		if (n >= 0)
		{
			int cnt;
			for (cnt = 0; cnt < n; cnt ++)
			{
				if (strstr(eps[cnt]->d_name, DB_TMP_SUFFIX))
				{
					snprintf(chk_tmp_file, _POSIX_PATH_MAX - 1, "%s/%s", SHM_DIR, eps[cnt]->d_name);
					unlink(chk_tmp_file);
				}
			}
		}
		
		else
		{
			// Cannot open shm directory
			return 1001;
		}
		
		// Uncompress;
		int fdi = open(db_file, O_RDONLY, (mode_t) 0666);
		if (-1 == fdi)
		{
			// Cannot open db file
			return 1002;
		}
		
		stat(db_file, &st);
		size_t db_filesize = st.st_size;
		
		// Two big buffer~~
		char *from = malloc(db_filesize);
		char *to = malloc(DB_FILE_SIZE);
		
		if (!from || !to)
		{
			// Memory alloc error
			return 1003;
		}
		
		read(fdi, from, db_filesize);
		size_t tmp_filesize = DB_FILE_SIZE;
		if (SNAPPY_OK != snappy_uncompress(from, db_filesize, to, &tmp_filesize))
		{
			// Uncompress failed
			return 1004;
		}
		
		fd = open(tmp_file, O_RDWR | O_CREAT, (mode_t) 0666);
		if (-1 == fd)
		{
			// Create file error
			return 1005;
		}
		
		write(fd, to, tmp_filesize);
	}
	
	lseek(fd, offset, SEEK_SET);
	char *res = malloc(item_size);
	
	if (!res)
	{
		// Memory error
		return 1006;
	}
	
	read(fd, res, item_size);
	fwrite(res, item_size, 1, stdout);
	
	close(fd);
	
	return 0;
}
